import {LogicTileEntity} from "../LogicTileEntity"
import {GameManager} from "../GameManager"
import {L2VMessageEnum} from "../../Framework/Network/L2VMessageEnum"
import {waitInterval} from "../../Framework/ResourceManager/ResLoader"

export enum TileType{
    BIRD = 0,
    FISH = 1,
    HORSE = 2,
    SWARD = 3,
    BOMB = 4,
    SPACE = 5,
}

export class Node<T>{
    obj:T;
    next:Node<T>;
    prev:Node<T>;
    constructor(obj:T){
        this.obj = obj;
        this.next = null;
        this.prev = null;
    }
}

export class LinkedList<T>{
    headNode:Node<T> = null;
    tailNode:Node<T> = null;
    size:number = 0;

    public push(obj:T):void{
        let node:Node<T> = new Node<T>(obj);
        if(this.size == 0){
            this.tailNode = node;
            this.headNode = node;
        }else{
            this.tailNode.next = node;
            node.prev = this.tailNode;
            this.tailNode = node;
        }
        this.size++;
    }

    public get(index:number): T {
        let node:Node<T> = this.headNode;
        if(node == null){
            return null;
        }
        while(index!=0){
            if(node.next == null){
                return null;
            }else{
                node = node.next;
            }
            index--;
        }
        return node.obj;
    }

    public pop():T{
        let node:Node<T> = this.headNode;
        if(node == null){
            return null;
        }else{
            if(node.next != null){
                node.next.prev = null;
            }
            this.headNode = node.next;
            this.size--;
            return node.obj;
        }
    }

    public contains(obj:T, compare:(a,b:T)=>boolean):boolean{
        for(let i:number = 0; i<this.size; i++){
            let o:T = this.get(i);
            //console.log("compare:",o,obj);
            if(compare(o, obj)){
                return true;
            }
        }
        return false;
    }

    public forAll(handle:(obj:T)=>void):void{
        let node:Node<T> = this.headNode;
        while(node != null){
            handle(node.obj);
            node = node.next;
        }
    }
}

/**
 * 描述一个块对象
 */
export class TileBlock{
    //类型
    value:number;
    x:number;
    y:number;
    //权重
    weight:number = 1;
    //entityID
    entityID:number = 0;

    set(x,y,value:number):void{
        this.x = x;
        this.y = y;
        this.value = value;
    }

    public toString = ():string =>{
        return "("+this.x+","+this.y+",[v:"+this.value+", w:"+this.weight+"])";
    }

    public equals(obj: TileBlock):boolean{
        if(this.x == obj.x && this.y == obj.y){
            return true;
        }else{
            return false;
        }
    }
}

/**
 * 描述整个二维矩阵
 */
export class TileMatrix{
    gameManager: GameManager;
    pool: TileBlock[][] = [];
    //total length of this matrix
    //length: number;
    //max item value
    maxValue: number;

    maxX:number;
    maxY:number;

    /**
     *
     * @param length, 阵列的长度
     * @param maxValue, 每个block的取值范围上线从0开始到maxValue
     */
    constructor(maxX,maxY:number, maxValue:number, gameManager:GameManager){
        this.gameManager = gameManager;
        this.maxX = maxX;
        this.maxY = maxY;
        this.maxValue = maxValue;
        for(let i:number = 0; i< this.maxX ; i++){
            this.pool[i] = [];
            for(let j:number = 0; j < this.maxY ; j++){
                //this.pool[i][j] = this.randomBlock(i,j);
                let tileBlock:TileBlock = this.randomBlock(i,j);
                this.uiCreateTile(tileBlock,true);
            }
        }
    }

    /**
     * 生成随机块
     * @param x
     * @param y
     */
    public randomBlock(x,y:number):TileBlock{
        let value = this.random();
        let bb = new TileBlock();
        bb.set(x,y,value);
        this.pool[x][y] = bb;
        return bb;
    }

    public randomBlockNoBomb(x,y:number):TileBlock{
        let value = this.randomNoBomb();
        let bb = new TileBlock();
        bb.set(x,y,value);
        this.pool[x][y] = bb;
        return bb;
    }

    public randomBlockWithBomb(x,y:number, hasBomb:boolean):TileBlock{
        let value = 0;
        if(hasBomb){
            value = this.random();
        }else{
            value = this.randomNoBomb();
        }
        let bb = new TileBlock();
        bb.set(x,y,value);
        this.pool[x][y] = bb;
        return bb;
    }

    /**
     * 调整炸弹和白板的生成算法
     * 4: bomb
     * 5: space
     */
    private random():number{
        let randomNum:number = Math.floor(Math.random()*(this.maxValue+1));
        //return randomNum;
        //bomb, space
        if(randomNum == TileType.SPACE){
            if(Math.floor(Math.random()*100)>=60){
                return randomNum;
            }else{
                randomNum = this.randomNoBomb();
            }
        }else if(randomNum == TileType.BOMB){
            if(Math.floor(Math.random()*100)>=45){
                return randomNum;
            }else{
                randomNum = this.randomNoBomb();
            }
        }
        return randomNum;
    }

    private randomNoBomb():number{
        return Math.floor(Math.random()*(TileType.SWARD+1));
    }

    up(x,y:number):TileBlock{
        return this.getElement(x-1,y);
    }

    down(x,y:number):TileBlock{
        return this.getElement(x+1,y);
    }

    left(x,y:number):TileBlock{
        return this.getElement(x,y-1);
    }

    right(x,y:number):TileBlock{
        return this.getElement(x,y+1);
    }

    setValue(x,y,value:number):void{
        this.pool[x][y].value = value;
    }

    setWeight(x,y,weight:number):void{
        this.pool[x][y].weight = weight;
    }

    getValue(x,y:number):number{
        if(x <0 || x>=this.maxX){
            return null;
        }else if(y < 0 || y >= this.maxY){
            return null;
        }else{
            return this.pool[x][y].value;
        }
    }
    
    getWeight(x,y:number):number{
        if(x <0 || x>=this.maxX){
            return null;
        }else if(y < 0 || y >= this.maxY){
            return null;
        }else{
            return this.pool[x][y].weight;
        }
    }

    getElement(x,y:number):TileBlock{
        if(x <0 || x>=this.maxX){
            return null;
        }else if(y < 0 || y >= this.maxY){
            return null;
        }else{
            return this.pool[x][y];
        }
    }

    /**
     * 计算某个位置的联通块数量，最小是1
     * @param x
     * @param y
     */
    calcPileCount(x,y:number):number{
        let stack:LinkedList<TileBlock> = new LinkedList<TileBlock>();
        let ele:TileBlock = this.getElement(x,y);
        this.search(x,y,ele.value,stack);
        return stack.size;
    }

    /**
     * 将某个位置为起点，遍历所有的连通块数量
     * @param x
     * @param y
     * @param stack
     */
    search(x,y,value:number, stack:LinkedList<TileBlock>):void{
        let currentEle = this.getElement(x,y);
        if(currentEle != null && !stack.contains(currentEle,
                (a,b) => a.x == b.x&& a.y==b.y
            )){
            stack.push(currentEle);
        }
        
        let eleValue = value;

        let leftEle:TileBlock = this.left(x,y);
        if(leftEle != null && this.checkSame(leftEle.value, value)){
            if(!stack.contains(leftEle,
                (a,b) => a.x == b.x&& a.y==b.y
            )){
                stack.push(leftEle);
                this.search(leftEle.x, leftEle.y, value, stack);
            }
        }

        let rightEle:TileBlock = this.right(x,y);
        if(rightEle != null && this.checkSame(rightEle.value, value)){
            if(!stack.contains(rightEle,
                (a,b) => a.x == b.x&& a.y==b.y
            )){
                stack.push(rightEle);
                this.search(rightEle.x, rightEle.y, value, stack);
            }
        }

        let upEle:TileBlock = this.up(x,y);
        if(upEle != null && this.checkSame(upEle.value,value)){
            if(!stack.contains(upEle,
                (a,b) => a.x == b.x&& a.y==b.y
            )){
                stack.push(upEle);
                this.search(upEle.x, upEle.y, value, stack);
            }
        }

        let downEle:TileBlock = this.down(x,y);
        if(downEle != null && this.checkSame(downEle.value,value)){
            if(!stack.contains(downEle,
                (a,b) => a.x == b.x&& a.y==b.y
            )){
                stack.push(downEle);
                this.search(downEle.x, downEle.y, value, stack);
            }
        }
    }

    /**
     * 判断白块
     * @param checkValue, 待检查块
     * @param orginalValue, 源块数值
     */
    private checkSame(checkValue,orginalValue:number):boolean{
        return checkValue == orginalValue;
        // if(checkValue == orginalValue){
        //     return true;
        // }else if(checkValue==TileType.SPACE){
        //     return true;
        // }else{
        //     return false;
        // }
    }

    /**
     * 将被删除的块上方的块下移，填充空白区域
     * reset matrix after delete.
     * move block down and left the above block.
     */
    public resetMatrix():LinkedList<TileBlock>{
        let moveBlocks:LinkedList<TileBlock> = new LinkedList<TileBlock>();
        for(let x = 0; x<this.maxX;x++){
            let stack = new LinkedList<TileBlock>();
            //move none-empty to list
            for(let y = this.maxY - 1; y>=0; y--){
                let bb = this.getElement(x,y);
                if(bb != null){
                    stack.push(bb);
                    this.delete(x,y);
                }
            }
            //put them into matrix again.
            for(let y = this.maxY - 1; y>=0; y--){
                let bb = stack.pop();
                if(bb == null){
                    break;
                }else{
                    this.pool[x][y] = bb;
                    if(x == bb.x && y == bb.y){
                        //note changed.
                    }else{
                        moveBlocks.push(bb);
                    }
                    bb.x = x;
                    bb.y = y;
                }
            }
        }
        return moveBlocks;
    }

    /**
     * 上升一层
     */
    public upLevel():void{
        //move up
        for(let x = 1; x < this.maxX; x++){
            for(let y = 0; y < this.maxY; y++){
                this.move(x,y, x-1, y);
            }
        }
        this.fillBlank();
    }

    /**
     * 删除块
     * @param x
     * @param y
     */
    public delete(x,y:number):TileBlock{
        let bb = this.getElement(x,y);
        if(bb != null){
            this.pool[x][y] = null;
        }
        return bb;
    }

    /**
     * 移动块
     * @param srcX
     * @param srcY
     * @param dstX
     * @param dstY
     */
    public move(srcX,srcY, dstX, dstY:number): TileBlock{
        let movEle:TileBlock = this.getElement(srcX, srcY);
        if(movEle == null)return null;

        this.pool[srcX][srcY] = null;
        this.pool[dstX][dstY] = movEle;
        if(movEle != null){
            movEle.x = dstX;
            movEle.y = dstY;
        }
        return movEle;
    }


    /**
     * 填充所有空块, return random block list
     */
    public fillBlank(): LinkedList<TileBlock>{
        let fillList = new LinkedList<TileBlock>();
        for(let i = 0; i< this.maxX; i++){
            for(let j = 0; j< this.maxY; j++){
                let bb = this.getElement(i,j);
                if(bb == null){
                    if(j==this.maxY-1){
                        bb = this.randomBlockNoBomb(i,j);
                    }else{
                        bb = this.randomBlock(i,j);
                    }
                    fillList.push(bb);
                }
            }
        }
        return fillList;
    }

    public toString = ():string =>{
        let tostr:string = "\n";
        for(let i:number = 0; i< this.maxX; i++) {
            tostr += "[";
            for (let j: number = 0; j < this.maxY; j++) {
                let bb = this.getElement(i,j);
                if(bb != null){
                    tostr += this.getElement(i,j).toString() + ", ";
                }else{
                    tostr += "null,";
                }
            }
            tostr += "]\n";
        }
        return tostr;
    }

    public uiCreateTile(bb:TileBlock, up:boolean):LogicTileEntity{
        console.log("createTile:", bb.x, bb.y, bb.value, bb.weight, up);
        let entityID = this.gameManager.createEntity(bb.value);
        bb.entityID = entityID;
        let tile:LogicTileEntity = <LogicTileEntity>this.gameManager.getEntity(entityID).instance;
        tile.tileBlock = bb;
        tile.sendEntityMessage(L2VMessageEnum.CreateEntity, bb.x, bb.y);
        tile.sendEntityMessage(L2VMessageEnum.OnTileSetPosition, bb.x, bb.y, up);
        return tile;
    }

    public uiDeleteTile(x,y:number):LogicTileEntity{
        //console.log("prepare to delete:",x,y);
        let delBB:TileBlock = this.delete(x,y);
        if(delBB != null){
            let delEntity:LogicTileEntity = <LogicTileEntity>this.gameManager.getEntity(delBB.entityID).instance;
            this.gameManager.delEntity(delBB.entityID);
            if(delEntity != null){
                delEntity.sendEntityMessage(L2VMessageEnum.DelEntity, x, y);
            }
            return delEntity;
        }else{
            return null;
        }
    }

    public uiRemoveRow(y:number):void{
        //remove row
        for(let xPos:number = 0; xPos < this.maxX; xPos++){
            this.uiDeleteTile(xPos, y);
        }
    }

    /**
     * 炸弹爆炸
     * @param x 
     * @param y 
     */
    public uiBombColumn(x,y:number):void{
        //remove column
        for(let yPos:number = 0; yPos<= this.maxY; yPos++){
            this.uiDeleteTile(x, yPos);
        }
    }

    /**
     * 向下落，填充下部空格
     */
    public uiAdjustBlocks():void{
        let movedBlocks:LinkedList<TileBlock> = this.resetMatrix();
        let movBB:TileBlock = movedBlocks.pop();
        while(movBB != null){
            console.log("prepare to update move:", movBB.x, movBB.y);    
            let movedEntityID = movBB.entityID;
            let movedEntity:LogicTileEntity = <LogicTileEntity>this.gameManager.getEntity(movedEntityID).instance;
            movedEntity.sendEntityMessage(L2VMessageEnum.OnTileSetPosition, movBB.x, movBB.y);
            movBB = movedBlocks.pop();
        }
    }

    public uiFillBlanks(up:boolean):void{
        let filledBlocks:LinkedList<TileBlock> = this.fillBlank();
        let filledBB:TileBlock = filledBlocks.pop();
        while(filledBB != null){
            console.log("prepare to create:", filledBB.x, filledBB.y);
            this.uiCreateTile(filledBB,up);
            filledBB = filledBlocks.pop();
        }
    }


    /**
     * 上升一层
     */
    public async uiUpLevel(){
        let hasBomb:boolean = false;
        //check if there is bomb in the first line
        let bombList:LinkedList<TileBlock> = new LinkedList<TileBlock>();
        for(let x:number = 0; x<this.maxX; x++){
            let ele:TileBlock = this.getElement(x,0);
            if(ele != null){
                if(ele.value == TileType.BOMB){
                    console.log("found bomb:", ele.x, ele.y);
                    bombList.push(ele);
                }    
            }
        }
        console.log("uiUP bomb size:", bombList.size);
        this.uiRemoveRow(0);
        await waitInterval(100);

        if(bombList.size>=1){
            hasBomb = true;
        }

        if(hasBomb){
            bombList.forAll(
                (bomb:TileBlock) =>{
                    this.uiBombColumn(bomb.x, bomb.y);
                }
            );
            await waitInterval(100);
        }
        
        //move up-ward
        for(let x:number = 0; x<this.maxX; x++){
            for(let y:number = 1; y<this.maxY; y++){
                let moveEle:TileBlock = this.move(x,y,x,y-1);
                if(moveEle == null)continue;
                let movedEntityID = moveEle.entityID;
                let movedEntity:LogicTileEntity = <LogicTileEntity>this.gameManager.getEntity(movedEntityID).instance;
                movedEntity.sendEntityMessage(L2VMessageEnum.OnTileSetPosition, moveEle.x, moveEle.y, false);
            }
        }

        //fill blanks
        if(hasBomb){
            //fill the last line
            for(let posX:number = 0; posX<this.maxX; posX++){
                let newBB:TileBlock = this.randomBlockNoBomb(posX, this.maxY-1);
                this.uiCreateTile(newBB,false);
            }
            await waitInterval(100);

            //fill bomb blanks
            {
                bombList.forAll(
                    (bomb:TileBlock) =>{
                        for(let posY:number = 0; posY<=this.maxY-2; posY++){
                            let posX = bomb.x;
                            let newBB:TileBlock = this.randomBlock(posX, posY);
                            this.uiCreateTile(newBB,true);
                        }
                    }
                );
            }
        }else{
            this.uiFillBlanks(false);
        }
    }

    /**
     * 
     * @param x 堆叠块
     * @param y 
     */
    public uiPile(x,y:number):TileBlock{
        console.log("pile:",x,y);
        
        // let stack:LinkedList<TileBlock> = new LinkedList<TileBlock>();
        // let pileBlock:TileBlock = this.getElement(x,y);
        // this.search(x,y,pileBlock.value,stack);

        let pileBlock:TileBlock = this.getElement(x,y);
        let stack:LinkedList<TileBlock> = this.uiSearch(x,y);

        console.log("after search:",x,y, ", size:", stack.size);
        if(stack.size>=3){
            pileBlock.weight = this.calcWeight(stack);
            let pileEntity = this.getEntity(pileBlock);
            if(pileEntity!= null){
                //fire weight message
                pileEntity.sendEntityMessage(L2VMessageEnum.OnTileNumber, this.weight2UI(pileBlock.weight));
            }else{
                console.log("pile block not found:", x, y);
                return null;
            }
            let delBB:TileBlock = stack.pop();
            while(delBB != null){
                console.log("check BB:", delBB.x, delBB.y," with ", x, y);
                if(delBB.x != x || delBB.y != y){
                    this.uiDeleteTile(delBB.x, delBB.y);
                    console.log("delBB:", delBB.x, delBB.y);
                }else{
                    console.log("ignore BB:", delBB.x, delBB.y);
                }
                delBB = stack.pop();
            }
            this.uiAdjustBlocks();
            this.uiFillBlanks(true);
        }else{
            return null;
        }
    }

    /**
     * 递归遍历，先查找所有连通的块。然后查找所有联通块对应的白板块。
     * 所以需要做二次遍历
     * @param x 
     * @param y 
     */
    public uiSearch(x,y:number):LinkedList<TileBlock>{
        let stack:LinkedList<TileBlock> = new LinkedList<TileBlock>();
        let ele:TileBlock = this.getElement(x,y);
        if(ele != null){
            this.search(x,y, ele.value, stack);
            //进一步搜索连接的白板
            if(stack.size >= 3){
                stack.forAll((bb)=>{
                    this.search(bb.x, bb.y, TileType.SPACE, stack);
                });
            }
        }
        return stack;
    }
    
    private weight2UI(weight:number):number{
        return Math.pow(weight,2);
    }

    private calcWeight(stack:LinkedList<TileBlock>):number{
        let node:Node<TileBlock> = stack.headNode;
        let total:number = 0;
        while(node!= null){
            total = total + node.obj.weight;
            node = node.next;
        }
        return total;
    }

    private getEntity(tileBlock:TileBlock):LogicTileEntity{
        let entity:LogicTileEntity = <LogicTileEntity>this.gameManager.getEntity(tileBlock.entityID).instance;
        return entity;
    }
}